/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package terminalapp;

import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author Kostya
 */
public class TerminalModel {

    List<TerminalModelChangedListener> changedListeners = new ArrayList<TerminalModelChangedListener>();

    List<StringBuffer> lines = new ArrayList<StringBuffer>();
    int maxLines = 200;
    int lineWidth = 75;
    int winHeight = 20;
    StringBuffer current = null;
    int x = 0;
    int y = 0;
    int charCount = 0;

    int savePosX = 0;
    int savePosY = 0;
    ConnectionHandler connectionHandler = null;

    public TerminalModel(ConnectionHandler aConnectionHandler) {
        connectionHandler = aConnectionHandler;
    }

    private void checkLineWidth(StringBuffer aBuffer, int aSize){
        for (int i = aBuffer.length(); i < aSize; i++) {
            aBuffer.append(' ');

        }
    }

    void acceptCharacter(int aCharacter){
        if(getLineCount()==0){
            current = new StringBuffer();
            lines.add(current);
        }
//        Log.debug(this, "Adding char ["+x+","+y+"]", aCharacter, "["+current+"]");
        charCount++;
        switch (aCharacter){
            case 10:
                Log.debug(this, "Switch to new line from", y, "of", getLineCount(),
                        "["+(y<getLineCount()?lines.get(y):"NO LINE")+"]");
                y++;
                if(y==getLineCount()){
                    current = new StringBuffer();
                    lines.add(current);
                }else{
                    current = lines.get(y);
                }
                break;
            case 13:
                x = 0;
                break;
            case 8:
                x--;
                break;
            case 7:
                Log.debug(this, "Bell!!!");
                break;
            case 0:
                //Do nothing
                break;
            default:
                checkLineWidth(current, x);
                if(x==lineWidth){
                    x = 0;
                    y++;
                    if(y>=getLineCount()){
                        current = new StringBuffer();
                        lines.add(current);
                    }else{
                        current = lines.get(y);
                    }
                }
                current.replace(x, x+1, ""+(char)aCharacter);
                x++;
//                Log.debug(this, "Added char ["+x+","+y+"]", aCharacter, "["+current+"]");
        }
        notifyTerminalModelChangedListeners();
    }

    private int getIntParameter(List<Integer> aBody, int index, int aDefault){
        int result = aDefault;
        String param = getParameter(aBody, index, Integer.toString(aDefault));
        try {
            result = Integer.parseInt(param);
        } catch (Throwable t) {
//            Log.error(this, "Exception thrown: " + t.getMessage());
//            t.printStackTrace();
        }
        return result;
    }
    private String getParameter(List<Integer> aBody, int index){
        return getParameter(aBody, index, "");
    }
    private String getParameter(List<Integer> aBody, int index, String aDefault){
        int paramCount = 0;
        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < aBody.size(); i++) {
            int ii = aBody.get(i);
            if(ii==0x3b){
                paramCount++;
                continue;
            }
            if(paramCount>index)
                return buffer.length()>0?buffer.toString(): aDefault;
            if(paramCount==index)
                buffer.append((char)ii);
        }
        return buffer.length()>0?buffer.toString(): aDefault;
    }

    void acceptEscSequence(ConnectionHandler.EscSequence aEscSequence, List<Integer> aBody){
//        Log.debug(this, "Accept sequence", (char)aEscSequence.endSeq, Integer.toHexString(aEscSequence.endSeq),
//                aBody.size());
        switch (aEscSequence.endSeq){
            case 0x7://BELL - Change win title
                Log.debug(this, "ESC["+charCount+"]["+x+","+y+"]["+aBody.size()+"]:Change win title: ",
                        getParameter(aBody, 0), getParameter(aBody, 1));
                notifyTerminalModelChangedListeners(getParameter(aBody, 1));
                break;
            case 0x4b://K - clear line
//                Log.debug(this, "Current line: ["+current+"]", x, current.charAt(x));
                Log.debug(this, "ESC["+charCount+"]["+x+","+y+"]["+aBody.size()+"]: clear line, param",
                        getParameter(aBody, 1, "0"), "["+current+"]");
                if(x<current.length()){
                    current = current.delete(x, current.length());
                    lines.set(y, current);
                }
//                x--;
//                Log.debug(this, "Current line: ["+current+"]", x, current.charAt(x));
                break;
            case 0x48://H - move cursor to x, y
                int newY = getIntParameter(aBody, 0, 1)-1;
                int newX = getIntParameter(aBody, 1, 1)-1;
                Log.debug(this, "ESC["+charCount+"]["+x+","+y+"]["+aBody.size()+"]: goto total: params:", newX, newY);
                x = newX;
                while(getLineCount()<Math.max(winHeight, newY+1)){
                    lines.add(new StringBuffer());
                }
                y = getLineCount()-winHeight+newY;
                if(y>=getLineCount())
                    y = getLineCount()-1;
                current = lines.get(y);
                break;
            case 0x4A://J - move top, clear screen
                x = 0;
                Log.debug(this, "ESC["+charCount+"]["+x+","+y+"]["+aBody.size()+"]: clrscr total:", getLineCount(),
                        "params:", getParameter(aBody, 0), getParameter(aBody, 1));
                while(getLineCount()<winHeight){
                    lines.add(new StringBuffer());
                }
                y = getLineCount()-winHeight;
                for (int i = y; i < getLineCount(); i++) {
                    lines.get(i).delete(0, lines.get(i).length());
                }
                current = lines.get(y);
                break;
            case 0x41: //A - Cursor up
                int step = getIntParameter(aBody, 0, 1);
                Log.debug(this, "ESC["+charCount+"]["+x+","+y+"]["+aBody.size()+"]: Move cursor UP from", y, "to", (y-step));
                y -= step;
                if(y<0)
                    y = 0;
                current = lines.get(y);
                break;
            case 0x42: //A - Cursor down
                step = getIntParameter(aBody, 0, 1);
                Log.debug(this, "ESC["+charCount+"]["+x+","+y+"]["+aBody.size()+"]: Move cursor DOWN from", y, "to", (y+step));
                y += step;
                while(getLineCount()<=y){
                    lines.add(new StringBuffer());
                }
                current = lines.get(y);
                break;
            case 0x43: //C - Cursor forward
                step = getIntParameter(aBody, 0, 1);
                Log.debug(this, "ESC["+charCount+"]["+x+","+y+"]["+aBody.size()+"]: Move cursor FORWARD from", x, "to", (x+step));
                x += step;
                break;
            case 0x44: //D - Cursor backward
                step = getIntParameter(aBody, 0, 1);
                Log.debug(this, "ESC["+charCount+"]["+x+","+y+"]["+aBody.size()+"]: Move cursor BACKWARD from", x, "to", (x-step));
                x -= step;
                break;
            case 0x47: //G - Cursor absolute
                step = getIntParameter(aBody, 0, 0);
                Log.debug(this, "ESC["+charCount+"]["+x+","+y+"]["+aBody.size()+"]: Move cursor absolute from", x, "to", step);
                x = step;
                break;
            case 0x50: //P - Delete char
                int chars = getIntParameter(aBody, 0, 1);
                Log.debug(this, "ESC["+charCount+"]["+x+","+y+"]["+aBody.size()+"]: Delete chars: ", x, chars);
                current = current.delete(x, x+chars);
                lines.set(y, current);
                break;
            case 0x68: //h - Set Mode
                Log.debug(this, "ESC["+charCount+"]["+x+","+y+"]["+aBody.size()+"]: set mode:", getParameter(aBody, 0, "<No param>"));
                break;
            case 0x72: //r - set scrolling region
                Log.debug(this, "ESC["+charCount+"]["+x+","+y+"]["+aBody.size()+"]: set scrolling region:",
                        getIntParameter(aBody, 0, 0), getIntParameter(aBody, 1, 0));
                break;
            case 0x6c: //l - reset mode
                Log.debug(this, "ESC["+charCount+"]["+x+","+y+"]["+aBody.size()+"]: reset mode: ", getParameter(aBody, 0, "<No param>"));
                break;
            case 0x73: //s - save state or so
                Log.debug(this, "ESC["+charCount+"]["+x+","+y+"]["+aBody.size()+"]: save mode: ", getParameter(aBody, 0, "<No param>"));
                break;
            case 0x6d://m - set color
                break;
            case -1:
                if(aEscSequence.startSeq.length<2)
                    break;
                switch (aEscSequence.startSeq[1]){
                    case 7://Save position
                        savePosX = x;
                        savePosY = y;
                        Log.debug(this, "Save pos", x, y);
                        break;
                    case 8://Restore position
                        Log.debug(this, "Restore pos from", x, y, "to", savePosX, savePosY);
                        x = savePosX;
                        y = savePosY;
                        current = lines.get(y);
                        break;

                }
                break;
            default:
                Log.debug(this, "ESC["+charCount+"]["+x+","+y+"]["+aBody.size()+"] OTHER:", (char)aEscSequence.endSeq, Integer.toHexString(aEscSequence.endSeq),
                        "params:", getIntParameter(aBody, 0, -1), getIntParameter(aBody, 1, -1));

        }
        notifyTerminalModelChangedListeners();
    }

    public void addTerminalModelChangedListener(TerminalModelChangedListener aChangedListener){
        changedListeners.add(aChangedListener);
    }

    private void notifyTerminalModelChangedListeners(){
        for (TerminalModelChangedListener terminalModelChangedListener : changedListeners) {
            terminalModelChangedListener.modelChanged();
        }
    }

    private void notifyTerminalModelChangedListeners(String aTitle){
        for (TerminalModelChangedListener terminalModelChangedListener : changedListeners) {
            terminalModelChangedListener.titleChanged(aTitle);
        }
    }

    public int getLineCount(){
        return lines.size();
    }

    public String getLine(int aIndex){
        return lines.get(aIndex).toString();
    }

    public void sendCharacters(int[] aCharacters){
        try {
            for (int i : aCharacters) {
                connectionHandler.sendChar(i);
            }
        } catch (Throwable t) {
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
        }
    }

    public void sendCharacter(int aCharacter){
        try {
            connectionHandler.sendChar(aCharacter);
        } catch (Throwable t) {
            Log.error(this, "Exception thrown: " + t.getMessage());
            t.printStackTrace();
        }
    }
}
